﻿/// <summary>
/// Developer: CHOOI KAH WAI
/// Last Modified: 21/11/2009
/// </summary>

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Media.Animation;
using System.Windows.Threading;
using System.Collections;
using ARSS.Common;
using System.Collections.ObjectModel;
using System.Windows.Media.Media3D;
using ARSS.Editor.Common;

namespace ARSS.Editor
{
	/// <summary>
	/// Interaction logic for AnimationWindow.xaml
	/// </summary>
	public partial class AnimationWindow : Window
    {
        #region Variables

        /// <summary>
        /// Timer for animation playback
        /// </summary>
        DispatcherTimer animationTimer;

        /// <summary>
        /// Do animation?
        /// </summary>
        private bool isAnimating;

        /// <summary>
        /// Temporary holder for scenenodes' positions
        /// </summary>
        private Dictionary<SceneNode, Vector3D> sceneNodes;

        #endregion

        #region Functions

        /// <summary>
        /// Constructor
        /// </summary>
        public AnimationWindow()
		{
			this.InitializeComponent();
			
			// Insert code required on object creation below this point.
            Loaded += new RoutedEventHandler(AnimationWindow_Loaded);

            animationTimer = new DispatcherTimer(TimeSpan.FromSeconds(0.01), DispatcherPriority.Normal, new EventHandler(OnAnimationTicks), this.Dispatcher);

            this.isAnimating = false;
		}

        /// <summary>
        /// Animation timer ticks
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnAnimationTicks(object sender, EventArgs e)
        {
            double currentTime = double.Parse(CurrentTimeTextBox.Text);
            currentTime += 0.01;
            double endTime = double.Parse(EndTimeTextBox.Text);

            if (currentTime >= endTime)
            {
                currentTime = endTime;
                this.isAnimating = false;
            }

            CurrentTimeTextBox.Text = currentTime.ToString("0.00");

            if (this.isAnimating == false)
                this.animationTimer.Stop();
        }


        /// <summary>
        /// Window loaded event handler
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void AnimationWindow_Loaded(object sender, RoutedEventArgs e)
        {
            LayoutRoot.DataContext = Core.Instance.SceneData;
            EndTimeTextBox_TextChanged(null, null);

            // Setup SceneNodeTreeView binding
            System.Collections.ObjectModel.ObservableCollection<SceneNode> nodes = new System.Collections.ObjectModel.ObservableCollection<SceneNode>();
            nodes.Add(Core.Instance.SceneData.Root);
            SceneNodeTreeView.ItemsSource = nodes;

            
            this.sceneNodes = new Dictionary<SceneNode, Vector3D>();
            ResetTemporarySceneNode(Core.Instance.SceneData.Root);

        }

        /// <summary>
        /// Reset the the temporary scene nodes holder
        /// </summary>
        /// <param name="node"></param>
        private void ResetTemporarySceneNode(SceneNode node)
        {
            this.sceneNodes.Add(node, new Vector3D(node.Translation.X, node.Translation.Y, node.Translation.Z));

            foreach (SceneNode child in node.Children)
            {
                ResetTemporarySceneNode(child);
            }
        }

        /// <summary>
        /// EndTimeTextBox TextChanged Event Handler
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void EndTimeTextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
        {
            if (!this.IsLoaded) return;

            double value = 0;
            if(double.TryParse(EndTimeTextBox.Text, out value))
            {
                if (value < 0)
                    value = 0;
            }

            TimelineSlider.Maximum = value;

            this.isAnimating = false;
        	
        }

        /// <summary>
        /// TimelineSlider ValueChanged Event Handler
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void TimelineSlider_ValueChanged(object sender, System.Windows.RoutedPropertyChangedEventArgs<double> e)
        {
            CurrentTimeTextBox.Text = TimelineSlider.Value.ToString("0.00");
        }

        /// <summary>
        /// CurrentTimeTextBox TextChanged Event Handler
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CurrentTimeTextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
        {
            if (!this.IsLoaded) return;

            double value = 0;
            if (double.TryParse(CurrentTimeTextBox.Text, out value))
            {
                if ((value < 0) || (value > double.Parse(EndTimeTextBox.Text)))
                    value = 0;
            }

            TimelineSlider.Value = value;

            CalculateSceneNodesPositions(value);
            UpdateSceneNodePositionsDisplay();
        }

        /// <summary>
        /// Is this keyframe contains given node?
        /// </summary>
        /// <param name="kf"></param>
        /// <param name="nodeName"></param>
        /// <returns></returns>
        private bool IsKeyFrameContainsNode(KeyFrame kf, string nodeName)
        {
            foreach(AnimationNode animNode in kf.Nodes)
            {
                if (animNode.Target.CompareTo(nodeName) == 0)
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Find the animation node targeting current node
        /// </summary>
        /// <param name="kf"></param>
        /// <param name="nodeName"></param>
        /// <returns></returns>
        private AnimationNode GetAnimationNodeFromKeyFrame(KeyFrame kf, string nodeName)
        {
            foreach (AnimationNode animNode in kf.Nodes)
            {
                if (animNode.Target.CompareTo(nodeName) == 0)
                {
                    return animNode;
                }
            }

            return null;
        }

        /// <summary>
        /// Calculate the new positions for all scene nodes
        /// </summary>
        /// <param name="value"></param>
        private void CalculateSceneNodesPositions(double currentTime)
        {
            if (KeyFrameListBox.ItemsSource == null) return;
            ObservableCollection<KeyFrame> frameList = (ObservableCollection<KeyFrame> )KeyFrameListBox.ItemsSource;

            if(frameList.Count == 1) return;

            SceneNode[] nodesBuffer = new SceneNode[this.sceneNodes.Count];
            this.sceneNodes.Keys.CopyTo(nodesBuffer, 0);
            foreach(SceneNode dummyNode in nodesBuffer)
            {
                KeyFrame firstKey = null;
                KeyFrame nextKey = null;
                int i = 0;

                // Find the first key nearest to current time
                for (i = (frameList.Count - 1); i > 0; i--)
                {
                    if (IsKeyFrameContainsNode(frameList[i], dummyNode.Name) && frameList[i].TSec < currentTime)
                        break;
                }
                firstKey = frameList[i];

                // Find the next key nearest to current time
                for (i = i + 1; i < frameList.Count; i++)
                {
                    if (IsKeyFrameContainsNode(frameList[i], dummyNode.Name))
                        break;
                }

                if (i == frameList.Count) // End of key frame list, no translation for this node
                    continue;

                nextKey = frameList[i];

                // Calculate the position here
                double currentTimePercentage = (currentTime - firstKey.TSec) / (nextKey.TSec - firstKey.TSec);

                AnimationNode firstAnimNode = GetAnimationNodeFromKeyFrame(firstKey, dummyNode.Name);
                AnimationNode nextAnimNode = GetAnimationNodeFromKeyFrame(nextKey, dummyNode.Name);

                Vector3D delta = Vector3D.Subtract(new Vector3D(nextAnimNode.X, nextAnimNode.Y, nextAnimNode.Z),
                                                    new Vector3D(firstAnimNode.X, firstAnimNode.Y, firstAnimNode.Z));

                this.sceneNodes[dummyNode] = Vector3D.Add(new Vector3D(firstAnimNode.X, firstAnimNode.Y, firstAnimNode.Z)
                                                            , Vector3D.Multiply(currentTimePercentage, delta));

                // Set the node position
                (dummyNode.Model3D.Transform as Transform3DGroup).Children[4] = new TranslateTransform3D(this.sceneNodes[dummyNode]);

            }
        }

        /// <summary>
        /// Play button clicked
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void PlayButton_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            if (this.isAnimating) return;

            this.isAnimating = true;
            this.animationTimer.Start();
        }

        /// <summary>
        /// Pause button clicked
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void PauseButton_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            if (!this.isAnimating) return;
            this.isAnimating = false;
            this.animationTimer.Stop();
        }

        /// <summary>
        /// Capture scene button clicked
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CaptureButton_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            PauseButton_Click(null, null);

            // Check whether we need to create new key frame or not
            KeyFrame keyFrame = null;
            double currentTime = double.Parse(CurrentTimeTextBox.Text);

            ObservableCollection<KeyFrame> frameList = (ObservableCollection<KeyFrame>)KeyFrameListBox.ItemsSource;

            foreach (KeyFrame bufferFrame in frameList)
            {
                if (bufferFrame.TSec == currentTime)
                {
                    keyFrame = bufferFrame;
                    break;
                }
            }

            if (keyFrame == null)
            {
                keyFrame = new KeyFrame();
                keyFrame.TSec = currentTime;
                frameList.Add(keyFrame);
            }
            //////////

            foreach (KeyValuePair<SceneNode, Vector3D> item in this.sceneNodes)
            {
                KeyFrame firstKey = null;
                int i = 0;

                // Find the first key nearest to current time
                for (i = (frameList.Count - 1); i > 0; i--)
                {
                    if (IsKeyFrameContainsNode(frameList[i], item.Key.Name) && frameList[i].TSec < currentTime)
                        break;
                }
                firstKey = frameList[i];

                AnimationNode firstAnimNode = GetAnimationNodeFromKeyFrame(firstKey, item.Key.Name);
                bool isNew = false;
                if (firstAnimNode == null)
                {
                    firstAnimNode = new AnimationNode();
                    firstAnimNode.Target = item.Key.Name;
                    firstAnimNode.X = item.Key.Translation.X;
                    firstAnimNode.Y = item.Key.Translation.Y;
                    firstAnimNode.Z = item.Key.Translation.Z;
                    isNew = true;
                }

                // Any changes?
                if ((firstAnimNode.X - item.Value.X + firstAnimNode.Y - item.Value.Y + firstAnimNode.Z - item.Value.Z) != 0)
                {
                    // Node translated, add new animationnode
                    AnimationNode newAnimNode = new AnimationNode();
                    newAnimNode.Target = item.Key.Name;
                    newAnimNode.X = item.Value.X;
                    newAnimNode.Y = item.Value.Y;
                    newAnimNode.Z = item.Value.Z;

                    keyFrame.Nodes.Add(newAnimNode);

                    if (isNew)
                        firstKey.Nodes.Add(firstAnimNode);
                }
            }
        }

        /// <summary>
        /// CurrentTimeTextBox got focus event handler
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CurrentTimeTextBox_GotFocus(object sender, System.Windows.RoutedEventArgs e)
        {
            // Pause animation
            PauseButton_Click(null, null);
        }

        /// <summary>
        /// EndTimeTextBox got focus event handler
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void EndTimeTextBox_GotFocus(object sender, System.Windows.RoutedEventArgs e)
        {
            // Pause animation
            PauseButton_Click(null, null);
        }

        /// <summary>
        /// TimelineSlider got focus event handler
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void TimelineSlider_GotFocus(object sender, System.Windows.RoutedEventArgs e)
        {
            // Pause animation
            PauseButton_Click(null, null);
        }

        /// <summary>
        /// Delete key frame button clicked
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void DeleteKeyFrameButton_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            if (KeyFrameListBox.SelectedItem == null) return;

            // Do not allow deletion of first key frame
            if ((KeyFrameListBox.SelectedItem as KeyFrame).TSec == 0)
            {
                MessageBox.Show("Could not delete first animation key frame.", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            // Delete the key frame
            ObservableCollection<KeyFrame> ary = KeyFrameListBox.ItemsSource as ObservableCollection<KeyFrame>;
            ary.RemoveAt(KeyFrameListBox.SelectedIndex);

        }

        /// <summary>
        /// AnimationSetListBox SelectionChanged event handler
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void AnimationSetListBox_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
        {
            // Disable DeleteButton and AnimationDataGroupBox if no animation is selected
            DeleteButton.IsEnabled = AnimationDataGroupBox.IsEnabled = (AnimationSetListBox.SelectedItem != null);
        }

        /// <summary>
        /// KeyFrameListBox SelectionChanged event handler
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void KeyFrameListBox_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
        {
            // Disable DeleteKeyFrameButton if no key frame is selected
            DeleteKeyFrameButton.IsEnabled = (KeyFrameListBox.SelectedItem != null);
        }

        /// <summary>
        /// AnimationDataGroupBox DataContextChanged event handler
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void AnimationDataGroupBox_DataContextChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e)
        {
            if(AnimationDataGroupBox.DataContext == null) return;

            KeyValuePair<string, ObservableCollection<KeyFrame>> source = (KeyValuePair<string, ObservableCollection<KeyFrame>>)AnimationDataGroupBox.DataContext;
            
            if(source.Value.Count <= 0) return;

            // Reset End Time to the last keyframe
            KeyFrame lastFrame = source.Value[source.Value.Count - 1] as KeyFrame;
            EndTimeTextBox.Text = lastFrame.TSec.ToString();
        }

        /// <summary>
        /// Delete set button clicked
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void DeleteButton_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            if (AnimationSetListBox.SelectedIndex < 0) return;

            KeyValuePair<string, ObservableCollection<KeyFrame>> selectedItem = (KeyValuePair<string, ObservableCollection<KeyFrame>>)AnimationSetListBox.SelectedItem;

            Core.Instance.SceneData.Animations.Remove(selectedItem.Key);

            // Redo data binding
            LayoutRoot.DataContext = null;
            LayoutRoot.DataContext = Core.Instance.SceneData;
        }

        /// <summary>
        /// New set button clicked
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void NewButton_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            NewAnimationSetWindow nasw = new NewAnimationSetWindow();
            Nullable<bool> result = nasw.ShowDialog();

            if (result.HasValue)
            {
                if (result.Value == true)
                {
                    // Always reset the scene nodes for each new animations
                    this.sceneNodes.Clear();
                    ResetSceneNodePositions();
                    ResetTemporarySceneNode(Core.Instance.SceneData.Root);

                    ObservableCollection<KeyFrame> keyFrames = new ObservableCollection<KeyFrame>();
                    KeyFrame kf = new KeyFrame();
                    kf.TSec = 0;
                    keyFrames.Add(kf);
                    Core.Instance.SceneData.Animations.Add(nasw.NameTextBox.Text, keyFrames);

                    // Redo data binding
                    LayoutRoot.DataContext = null;
                    LayoutRoot.DataContext = Core.Instance.SceneData;
                }
            }
        }

        /// <summary>
        /// User changed the scene node selection
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SceneNodeTreeView_SelectedItemChanged(object sender, System.Windows.RoutedPropertyChangedEventArgs<object> e)
        {
            UpdateSceneNodePositionsDisplay();
        }

        /// <summary>
        /// Update the scene node positions display
        /// </summary>
        private void UpdateSceneNodePositionsDisplay()
        {
            double x = 0;
            double y = 0;
            double z = 0;

            if (SceneNodeTreeView.SelectedItem != null)
            {
                Vector3D vec = this.sceneNodes[SceneNodeTreeView.SelectedItem as SceneNode];
                x = vec.X;
                y = vec.Y;
                z = vec.Z;
            }

            XPosTextBox.Text = x.ToString();
            YPosTextBox.Text = y.ToString();
            ZPosTextBox.Text = z.ToString();
        }

        /// <summary>
        /// Window closing event handler
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            ResetSceneNodePositions();
        }

        /// <summary>
        /// Reset scene nodes positions
        /// </summary>
        private void ResetSceneNodePositions()
        {
            foreach (SceneNode node in this.sceneNodes.Keys)
            {
                if (node.Model3D == null) continue;

                (node.Model3D.Transform as Transform3DGroup).Children[4] = new TranslateTransform3D(node.Translation);
            }
        }

        private void PositionTextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
        {
            if ((this.isAnimating) || (SceneNodeTreeView.SelectedItem == null)) return;
            
            double x = 0;
            double y = 0;
            double z = 0;

            if (!(double.TryParse(XPosTextBox.Text, out x) &&
                double.TryParse(YPosTextBox.Text, out y) &&
                double.TryParse(ZPosTextBox.Text, out z)))
            {
                MessageBox.Show("Invalid position", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            Vector3D vec = new Vector3D(x, y, z);
            SceneNode node = SceneNodeTreeView.SelectedItem as SceneNode;
            this.sceneNodes[node] = vec;

            (node.Model3D.Transform as Transform3DGroup).Children[4] = new TranslateTransform3D(vec);

            
        }


        #endregion
    }
}